/*
 * Copyright (C) 2019 xuexiangjys(xuexiangjys@163.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.xuexiang.xui_lib.component.progress;

import com.xuexiang.xui_lib.component.progress.ratingbar.AttrValueUtil;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrHelper;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.LinearShader;
import ohos.agp.render.Paint;
import ohos.agp.render.Shader;
import ohos.agp.utils.Color;
import ohos.agp.utils.LayoutAlignment;
import ohos.agp.utils.Point;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;

/**
 * HorizontalProgressView
 *
 * @author xuexiang
 * @since 2019-05-12 12:34
 */
public class HorizontalProgressView extends Component {
    /**
     * animation types supported
     */
    public static final int ACCELERATE_DECELERATE_INTERPOLATOR = 0;
    private static final int LINEAR_INTERPOLATOR = 1;
    private static final int ACCELERATE_INTERPOLATOR = 2;
    private static final int DECELERATE_INTERPOLATOR = 3;
    private static final int OVERSHOOT_INTERPOLATOR = 4;
    private DrawTask drawTask;
    /**
     * the type of animation
     */
    private int mAnimateType = 0;
    /**
     * the progress of start point
     */
    private float mStartProgress = 0;
    /**
     * the progress of end point
     */
    private float mEndProgress = 60;
    /**
     * the color of start progress
     */
    private Color mStartColor;
    /**
     * the color of end progress
     */
    private Color mEndColor;
    /**
     * has track of moving or not
     */
    private boolean trackEnabled = false;
    /**
     * the stroke width of progress
     */
    private int mTrackWidth = 6;
    /**
     * the size of inner text
     */
    private int mProgressTextSize = 48;
    /**
     * the color of inner text
     */
    private Color mProgressTextColor;
    /**
     * the color of progress track
     */
    private Color mTrackColor;
    /**
     * the duration of progress moving
     */
    private int mProgressDuration = 1200;
    /**
     * show the inner text or not
     */
    private boolean textVisibility = true;
    /**
     * the round rect corner radius
     */
    private int mCornerRadius = 30;
    /**
     * the offset of text padding bottom
     */
    private int mTextPaddingBottomOffset = 5;
    /**
     * moving the text with progress or not
     */
    private boolean isTextMoved = true;

    /**
     * the animator of progress moving
     */
    private AnimatorValue progressAnimator;
    /**
     * the progress of moving
     */
    private float moveProgress = 0;
    /**
     * the paint of drawing progress
     */
    private Paint progressPaint;
    /**
     * the gradient of color
     */
    private LinearShader mShader = null;
    /**
     * the oval's rect shape
     */
    private RectFloat mRect;
    private RectFloat mTrackRect;
    private int curveType;
    private HorizontalProgressUpdateListener animatorUpdateListener;

   /* *//**
     * HorizontalProgressView
     *
     * @param context
     *//*
    public HorizontalProgressView(Context context) {
        this(context, null);
    }*/

    /**
     * HorizontalProgressView
     *
     * @param context
     * @param attrs
     */
    public HorizontalProgressView(Context context, AttrSet attrs) {
        this(context, attrs, 0);
    }

    /**
     * HorizontalProgressView
     *
     * @param context
     * @param attrs
     * @param defStyleAttr
     */
    public HorizontalProgressView(Context context, AttrSet attrs, int defStyleAttr) {
        super(context, attrs);
        obtainAttrs(context, attrs, defStyleAttr);
        init();
    }

    private void obtainAttrs(Context context, AttrSet attrs, int defStyleAttr) {
        mStartProgress = AttrValueUtil.get(attrs, "hpv_start_progress", 0);
        mEndProgress = AttrValueUtil.get(attrs, "hpv_end_progress", 60);
        if (attrs.getAttr("hpv_start_color").isPresent()) {
            mStartColor = attrs.getAttr("hpv_start_color").get().getColorValue();
        } else {
            mStartColor = new Color(Color.getIntColor("#FFC288"));
        }
        if (attrs.getAttr("hpv_end_color").isPresent()) {
            mEndColor = attrs.getAttr("hpv_end_color").get().getColorValue();
        } else {
            mEndColor = new Color(Color.getIntColor("#FFC288"));
        }
        trackEnabled = AttrValueUtil.get(attrs, "hpv_isTracked", false);
        if (attrs.getAttr("hpv_progress_textColor").isPresent()) {
            mProgressTextColor = attrs.getAttr("hpv_progress_textColor").get().getColorValue();
        } else {
            mProgressTextColor = new Color(Color.getIntColor("#696969"));
        }

        mProgressTextSize = AttrValueUtil.get(attrs, "hpv_progress_textSize", AttrHelper.fp2px(14, getContext()));
        mTrackWidth = AttrValueUtil.get(attrs, "hpv_track_width", AttrHelper.vp2px(16, getContext()));
        mAnimateType = AttrValueUtil.get(attrs, "hpv_animate_type", ACCELERATE_DECELERATE_INTERPOLATOR);
        if (attrs.getAttr("hpv_track_color").isPresent()) {
            mTrackColor = attrs.getAttr("hpv_track_color").get().getColorValue();
        } else {
            mTrackColor = new Color(Color.getIntColor("#F3E5DD"));
        }

        textVisibility = AttrValueUtil.get(attrs, "hpv_progress_textVisibility", true);
        mProgressDuration = AttrValueUtil.get(attrs, "hpv_progress_duration", 1200);
        mCornerRadius = AttrValueUtil.get(attrs, "hpv_corner_radius", AttrHelper.vp2px(5, getContext()));
        mTextPaddingBottomOffset = AttrValueUtil.get(attrs, "hpv_text_padding_bottom",
            AttrHelper.vp2px(5, getContext()));
        isTextMoved = AttrValueUtil.get(attrs, "hpv_text_movedEnable", true);
        moveProgress = mStartProgress;
        setAnimateType(mAnimateType);
    }

    private void init() {
        progressPaint = new Paint();
        progressPaint.setAntiAlias(true);
        progressPaint.setStyle(Paint.Style.FILL_STYLE);
        initListener();
    }

    private void initListener() {
        drawTask = new DrawTask() {
            @Override
            public void onDraw(Component component, Canvas canvas) {
                updateTheTrack();
                drawTrack(canvas);
                setmShader();
                progressPaint.setShader(mShader, Paint.ShaderType.LINEAR_SHADER);
                canvas.drawRoundRect(mRect, mCornerRadius, mCornerRadius, progressPaint);

                drawProgressText(canvas);
            }
        };
        addDrawTask(drawTask);
    }

    @Override
    public void addDrawTask(DrawTask task) {
        super.addDrawTask(task);
    }

    private void setmShader() {
        mShader = new LinearShader(new Point[]{new Point(getPaddingLeft() - 50, (getHeight() - getPaddingTop()) - 50),
            new Point(getWidth() - getPaddingRight(), getHeight() / 2F + getPaddingTop() + mTrackWidth)},
            new float[]{},
            new Color[]{mStartColor, mEndColor},
            Shader.TileMode.CLAMP_TILEMODE);
    }

    /**
     * draw the track(moving background)
     *
     * @param canvas mCanvas
     */
    private void drawTrack(Canvas canvas) {
        if (trackEnabled) {
            progressPaint.setShader(null, Paint.ShaderType.LINEAR_SHADER);
            progressPaint.setColor(mTrackColor);
            canvas.drawRoundRect(mTrackRect, mCornerRadius, mCornerRadius, progressPaint);
        }
    }

    /**
     * draw the progress text
     *
     * @param canvas mCanvas
     */
    private void drawProgressText(Canvas canvas) {
        if (textVisibility) {
            Paint mTextPaint = new Paint();
            mTextPaint.setAntiAlias(true);
            mTextPaint.setStyle(Paint.Style.FILL_STYLE);
            mTextPaint.setTextSize(mProgressTextSize);
            mTextPaint.setColor(mProgressTextColor);
            mTextPaint.setTextAlign(LayoutAlignment.CENTER);

            String progressText = ((int) moveProgress) + "%";
            if (isTextMoved) {
                canvas.drawText(mTextPaint, progressText,
                    (getWidth() - getPaddingLeft() - getPaddingRight() - vp2px(getContext(), 28))
                        * (moveProgress / 100) + vp2px(getContext(), 10),
                    getHeight() / 2F - getPaddingTop() - mTextPaddingBottomOffset);
            } else {
                canvas.drawText(mTextPaint, progressText, (getWidth() - getPaddingLeft()) / 2F,
                    getHeight() / 2F - getPaddingTop() - mTextPaddingBottomOffset);
            }
        }
    }

    /**
     * set progress animate type
     *
     * @param type anim type
     */
    public void setAnimateType(int type) {
        this.mAnimateType = type;
        setObjectAnimatorType(type);
    }

    /**
     * set object animation type by received
     *
     * @param animatorType object anim type
     */
    private void setObjectAnimatorType(int animatorType) {
        switch (animatorType) {
            case ACCELERATE_DECELERATE_INTERPOLATOR:
                if (curveType != 0) {
                    curveType = 0;
                }
                curveType = Animator.CurveType.ACCELERATE_DECELERATE;
                break;
            case LINEAR_INTERPOLATOR:
                if (curveType != 0) {
                    curveType = 0;
                }
                curveType = Animator.CurveType.LINEAR;
                break;
            case ACCELERATE_INTERPOLATOR:
                if (curveType != 0) {
                    curveType = 0;
                    curveType = Animator.CurveType.ACCELERATE;
                }
                break;
            case DECELERATE_INTERPOLATOR:
                if (curveType != 0) {
                    curveType = 0;
                }
                curveType = Animator.CurveType.DECELERATE;
                break;
            case OVERSHOOT_INTERPOLATOR:
                if (curveType != 0) {
                    curveType = 0;
                }
                curveType = Animator.CurveType.OVERSHOOT;
                break;
            default:
                break;
        }
    }

    /**
     * set move progress
     *
     * @param progress progress of moving
     */
    public void setProgress(float progress) {
        this.moveProgress = progress;
        refreshTheView();
    }

    public float getProgress() {
        return this.moveProgress;
    }

    /**
     * set start progress
     *
     * @param startProgress
     * @throws IllegalArgumentException
     */
    public void setStartProgress(float startProgress) throws IllegalArgumentException {
        if (startProgress < 0 || startProgress > 100) {
            throw new IllegalArgumentException("Illegal progress value, please change it!");
        }
        this.mStartProgress = startProgress;
        this.moveProgress = mStartProgress;
        refreshTheView();
    }

    /**
     * set end progress
     *
     * @param endProgress end progress
     * @throws IllegalArgumentException
     */
    public void setEndProgress(float endProgress) throws IllegalArgumentException {
        if (endProgress < 0 || endProgress > 100) {
            throw new IllegalArgumentException("Illegal progress value, please change it!");
        }
        this.mEndProgress = endProgress;
        refreshTheView();
    }

    /**
     * set start color
     *
     * @param startColor start point color
     */
    public void setStartColor(Color startColor) {
        this.mStartColor = startColor;
        mShader = new LinearShader(new Point[]{new Point(getPaddingLeft() - 50, (getHeight() - getPaddingTop()) - 50),
            new Point(getWidth() - getPaddingRight(), getHeight() / 2F + getPaddingTop() + mTrackWidth)},
            new float[]{},
            new Color[]{mStartColor, mEndColor},
            Shader.TileMode.CLAMP_TILEMODE);

        refreshTheView();
    }

    /**
     * set end color
     *
     * @param endColor end point color
     */
    public void setEndColor(Color endColor) {
        this.mEndColor = endColor;
        mShader = new LinearShader(new Point[]{new Point(getPaddingLeft() - 50, (getHeight() - getPaddingTop()) - 50),
            new Point(getWidth() - getPaddingRight(), getHeight() / 2F + getPaddingTop() + mTrackWidth)},
            new float[]{},
            new Color[]{mStartColor, mEndColor},
            Shader.TileMode.CLAMP_TILEMODE);

        refreshTheView();
    }

    /**
     * set the width of progress stroke
     *
     * @param width stroke
     */
    public void setTrackWidth(int width) {
        this.mTrackWidth = vp2px(getContext(), width);
        refreshTheView();
    }

    /**
     * set track color for progress background
     *
     * @param color bg color
     */
    public void setTrackColor(Color color) {
        this.mTrackColor = color;
        refreshTheView();
    }

    /**
     * set text color for progress text
     *
     * @param textColor
     */
    public void setProgressTextColor(Color textColor) {
        this.mProgressTextColor = textColor;
    }

    /**
     * set text size for inner text
     *
     * @param size text size
     */
    public void setProgressTextSize(int size) {
        mProgressTextSize = vp2px(getContext(), size);
        refreshTheView();
    }

    /**
     * set duration of progress moving
     *
     * @param duration
     */
    public void setProgressDuration(int duration) {
        this.mProgressDuration = duration;
    }

    /**
     * set track for progress
     *
     * @param trackAble whether track or not
     */
    public void setTrackEnabled(boolean trackAble) {
        this.trackEnabled = trackAble;
        refreshTheView();
    }

    /**
     * set the visibility for progress inner text
     *
     * @param visibility text visible or not
     */
    public void setProgressTextVisibility(boolean visibility) {
        this.textVisibility = visibility;
        refreshTheView();
    }

    /**
     * set progress text moving with progress view or not
     *
     * @param moved
     */
    public void setProgressTextMoved(boolean moved) {
        this.isTextMoved = moved;
    }

    /**
     * start the progress's moving
     */
    public void startProgressAnimation() {
        progressAnimator = new AnimatorValue();
        progressAnimator.setCurveType(curveType);
        progressAnimator.setDuration(mProgressDuration);
        progressAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float progress) {
                if (animatorUpdateListener != null) {
                    animatorUpdateListener.onHorizontalProgressUpdate(
                        HorizontalProgressView.this, progress * mEndProgress);
                }

                moveProgress = progress * mEndProgress;
                invalidate();
            }
        });
        progressAnimator.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {
                if (animatorUpdateListener != null) {
                    animatorUpdateListener.onHorizontalProgressStart(HorizontalProgressView.this);
                }
            }

            @Override
            public void onStop(Animator animator) {
            }

            @Override
            public void onCancel(Animator animator) {
            }

            @Override
            public void onEnd(Animator animator) {
                if (animatorUpdateListener != null) {
                    animatorUpdateListener.onHorizontalProgressFinished(HorizontalProgressView.this);
                }
            }

            @Override
            public void onPause(Animator animator) {
            }

            @Override
            public void onResume(Animator animator) {
            }
        });
        progressAnimator.start();
    }

    /**
     * stop the progress moving
     */
    public void stopProgressAnimation() {
        if (progressAnimator != null) {
            progressAnimator.cancel();
            progressAnimator = null;
        }
    }

    /**
     * set the corner radius for the rect of progress
     *
     * @param radius the corner radius
     */
    public void setProgressCornerRadius(int radius) {
        this.mCornerRadius = vp2px(getContext(), radius);
        refreshTheView();
    }

    /**
     * set the text padding bottom offset
     *
     * @param offset the value of padding bottom
     */
    public void setProgressTextPaddingBottom(int offset) {
        this.mTextPaddingBottomOffset = vp2px(getContext(), offset);
    }

    /**
     * refresh the layout
     */
    private void refreshTheView() {
        invalidate();
    }

    /**
     * update the oval progress track
     */
    private void updateTheTrack() {
        mRect = new RectFloat(getPaddingLeft() + mStartProgress * (getWidth()
            - getPaddingLeft() - getPaddingRight() + 60) / 100,
            getHeight() / 2F - getPaddingTop(),
            (getWidth() - getPaddingRight() - 20) * (moveProgress / 100),
            getHeight() / 2F + getPaddingTop() + mTrackWidth);
        mTrackRect = new RectFloat(getPaddingLeft(), getHeight() / 2F - getPaddingTop(),
            getWidth() - getPaddingRight() - 20,
            getHeight() / 2F + getPaddingTop() + mTrackWidth);
    }

    /**
     * 进度条更新监听
     */
    public interface HorizontalProgressUpdateListener {
        /**
         * 进度条开始更新
         *
         * @param view
         */
        void onHorizontalProgressStart(Component view);

        /**
         * 进度条更新中
         *
         * @param view
         * @param progress
         */
        void onHorizontalProgressUpdate(Component view, float progress);

        /**
         * 进度条更新结束
         *
         * @param view
         */
        void onHorizontalProgressFinished(Component view);
    }

    /**
     * set the progress update listener for progress view
     *
     * @param listener update listener
     */
    public void setProgressViewUpdateListener(HorizontalProgressUpdateListener listener) {
        this.animatorUpdateListener = listener;
    }

    /**
     * vp2px
     *
     * @param context
     * @param dpValue
     * @return int
     */
    public int vp2px(Context context, float dpValue) {
        final float scale = AttrHelper.vp2px(dpValue, context);
        return (int) (dpValue * scale + 0.5f);
    }
}
